<?php
/**
 * 
 * @todo description of this class
 * 
 * @author Alton Crossley <crossleyframework@nogahidebootstrap.com>
 * @package Crossley Framework
 *  
 * @copyright Copyright (c) 2003-2009, Nogaide BootStrap INC. All rights reserved.
 * @license BSD http://opensource.org/licenses/bsd-license.php
 * @version $Id:$
 * 
 */
/**
 * database row pager using datasets/tableobjects
 * This class outputs tabular data in a table broken down into pages
 *
 * @author Alton Crossley
 * @package Crossley Framework
 * @version 0.1
 */

class X_Data_Grid implements Iterator, ArrayAccess
{
    /**
     * field names that each column represents
     * Note: to set the order of the columns, predefine this
     *
     * @var array
     */
    public $aColumns = array();
    
    /**
     * formated, human readable titles for use as header column titles
     *
     * <code>
     * array(
     *      <column_name> => <title>
     *  )
     * </code>
     * @var array
     */
    public $aColumnTitles = array();
    /**
     * stored data rows
     *
     * @var array
     */
    public $aRows = array();
    
    /**
     * table refrence
     *
     * @var X_DOM_Table
     */
    public $oTable;
    
    /**
     * field names of columns not to show in the grid
     *
     * @var array
     */
    protected $_aHideColumns = array();
    
    /**
     * field names of columns to show in the grid
     * Note: columns in $aHideColumns are still hidden
     * Note: if any columns are defined, only the defined columns will be shown,
     * to show all columns, leave this and $aHideColumns empty
     *
     * @var array
     */
    protected $_aShowColumns = array();
    
    /**
     * constructor will set data if an array, collection or dataset are passed
     * CAUTION: if you add subject data here, it will not be filtered
     *
     * @param unknown_type $xSubject array, collection or dataset
     */
    function __construct ($xSubject = null)
    {
        if (! empty($xSubject)) $this->addData($xSubject);
    
    }
    
    /**
     * add rows to the grid from known data objects
     * CAUTION: this adds array items and composit rows.  It is recomended that
     * you set your filters if any before you add data to the grid.
     *
     * @param unknown_type $xSubject
     * @return bool true if data type identified and used
     */
    public function addData ($xSubject)
    {
        switch (true)
        {
            case (is_array($xSubject)):
                return $this->addWithArray($xSubject);
                break;
            case ($xSubject instanceof X_Abstract_Object_Collection):
                return $this->addWithCollection($xSubject);
                break;
            case ($xSubject instanceof X_Abstract_Object):
                return $this->addWithDataset($xSubject);
                break;
            default:
                return false;
                break;
        }
        
        return true;
    }
    
    /**
     * get the table composit
     *
     * @return X_DOM_Table
     */
    public function getComposit ()
    {
        if (empty($this->oTable))
        {
            $this->oTable = new X_DOM_Table();
            
            $this->oTable->setHeadings(array_merge($this->aColumns, $this->aColumnTitles))->setMatchedBody($this->aRows);
        }
        return $this->oTable;
    }
    
    public function getArray ()
    {
        return $this->aRows;
    }
    
    public function setHideColumns (Array $aColumns)
    {
        if (! empty($aColumns)) $this->_aHideColumns = array_combine($aColumns, $aColumns);
    }
    
    public function setShowColumns (Array $aColumns)
    {
        if (! empty($aColumns)) $this->_aShowColumns = array_combine($aColumns, $aColumns);
    }
    
    /**
     * populate data using a collection
     *
     * @param X_Abstract_Object_Collection $oCollection
     */
    public function addWithCollection (X_Abstract_Object_Collection $oCollection)
    {
        foreach ($oCollection as $xItem)
        {
            if ($xItem instanceof X_Abstract_Object)
            {
                $this->addObjectAsRow($xItem);
            }
            elseif (is_array($xItem))
            {
                $this->addRow($xItem);
            }
            
        // we cant use anything elese... moving on...
        }
    
    }
    
    /**
     * populate data using an array of objects
     *
     * @param array $aObjects
     */
    public function addWithObjectArray (Array $aObjects)
    {
        foreach ($aObjects as $xItem)
        {
            if ($xItem instanceof X_Abstract_Object)
            {
                $this->addObjectAsRow($xItem);
            }
        }
    }
    
    /**
     * populate data using a 2D array
     *
     * @param array $aArray
     */
    public function addWithArray (Array $aArray)
    {
        if (current($aArray) instanceof X_Abstract_Object)
        {
            $this->addWithObjectArray($xSubject);
        }
        
        foreach ($aArray as $xItem)
        {
            if (is_array($xItem))
            {
                $this->addRow($xItem);
            }
        }
    
    }
    
    /**
     * use a dataset to populate data
     * NOTE: assumes a 2D/Table of values stored in the dataset
     *
     * @param X_Abstract_Object $oObject
     */
    public function addWithDataset (X_Abstract_Object $oObject)
    {
        $oObject->bExposeAttributes = true;
        $this->addWithArray($oItem->aAttributes);
    }
    
    /**
     * add a row saving off the keys for headers and filtering out hidden fields
     *
     * @param array $aArray
     */
    public function addRow (Array $aArray)
    {
        // adding keys as column names for filtering values
        //(removes specified hidden columns)
        $oKeys = array_keys($aArray);
        $aColumns = array_merge($this->aColumns, array_diff($oKeys, $this->_aHideColumns));
        
        if (! empty($this->_aShowColumns))
        {
            $aColumns = array_intersect($aColumns, $this->_aShowColumns);
        }
        
        if (! empty($aColumns))
        {
            $this->aColumns = array_combine($aColumns, $aColumns);
        }
        
        // filter values
        $aArray = array_intersect_key($aArray, $this->aColumns);
        
        // add values to our rows
        array_push($this->aRows, $aArray);
    }
    
    /**
     * ad a row from an abstract object
     *
     * @param X_Abstract_Object $oItem
     */
    public function addObjectAsRow (X_Abstract_Object $oItem)
    {
        $oItem->bExposeAttributes = true;
        $this->addRow($oItem->aAttributes);
    }
    
    public function getColumn ($sTitleKey)
    {
        if (! $this->oTable)
        {
            $this->getComposit();
        }
        
        return $this->oTable->getColumn($sTitleKey);
    }
    
    public function __toString ()
    {
        return (string) $this->getComposit();
    }
    
    // -- ITERATOR IMPLEMENTATIUON: iterates over body rows
    public function rewind ()
    {
        $this->oTable->sort();
        
        if ($this->valid())
        {
            reset($this->oTable->oBody->aChildren);
        }
    
    }
    
    public function current ()
    {
        if (! $this->valid()) return false;
        return current($this->oTable->oBody->aChildren);
    }
    
    public function key ()
    {
        if (! $this->valid()) return false;
        return key($this->oTable->oBody->aChildren);
    }
    
    public function next ()
    {
        if (! $this->valid()) return false;
        next($this->oTable->oBody->aChildren);
        return $this->oTable->oBody->current();
    }
    
    public function valid ()
    {
        if (empty($this->oTable) || empty($this->oTable->oBody) || empty($this->oTable->oBody->aChildren))
        {
            switch (true)
            {
                case (empty($this->oTable)):
                    X_Debug::out('Table is empty');
                case (empty($this->oTable->oBody)):
                    X_Debug::out('Body is empty');
                default:
                    X_Debug::out('Children is empty');
                    break;
            }
            return false;
        }
        return (current($this->oTable->oBody->aChildren));
    }
    
    // ARRAYACCESS IMPLEMENTATION
    /**
     * Assigns a value to the specified offset
     *
     * @param string $xKey
     * @param string $xValue
     */
    public public function offsetSet ($xKey, $xValue)
    {
        trigger_error(__CLASS__ . ' does not support setting array values', E_USER_WARNING);
    }
    /**
     * Whether or not an offset exists
     *
     * @param string $xKey
     * @return bool
     */
    public public function offsetExists ($xKey)
    {
        return array_key_exists($xKey, $this->oTable->oBody->aChildren);
    }
    /**
     * Unsets an offset
     *
     * @param string $xKey
     */
    public public function offsetUnset ($xKey)
    {
        unset($this->oTable->oBody->aChildren[$xKey]);
    }
    /**
     * Returns the value at specified offset
     *
     * @param string $xKey
     * @return X_DOM_Table_Data
     */
    public public function offsetGet ($xKey)
    {
        return (array_key_exists($xKey, $this->oTable->oBody->aChildren)) ? $this->oTable->oBody->aChildren[$xKey] : null;
    }
}